#include <asm/io.h>
#include <asm/system.h>
#include <cnix/string.h>
#include <cnix/wait.h>
#include <cnix/kernel.h>
#include <cnix/driver.h>
#include <cnix/errno.h>
#include <cnix/const.h>

extern unsigned char leds;
extern void setleds(void);

#define color_ch(console, ch) \
do{ \
	ch = ch | console->c_attr; \
	if(console->c_color_reverse) \
		ch ^= 0xff00; \
}while(0)

static void back_copy(unsigned short * dest, unsigned short * src, int count)
{
	unsigned short * d = dest + count, * s = src + count;

	for(; count >= 2; count -= 2){
		d -= 2;
		s -= 2;
		*((unsigned long *)d) = *((unsigned long *)s);
	}

	if(count >= 1){
		d -= 1;
		s-= 1;
		*d = *s;
	}
}

static void front_copy(unsigned short * dest, unsigned short * src, int count)
{
	for(; count >= 2; count -= 2){
		*((unsigned long *)dest) = *((unsigned long *)src);
		dest += 2;
		src += 2;
	}

	if(count >= 1)
		*dest = *src;
}

static void vidmem_set(unsigned short * dest, unsigned short ch, int count)
{
	for(; count > 0; count--)
		*dest++ = ch;
}

#define VGA_CRT_INX	0x3d4
#define VGA_CRT_DATA 	0x3d5

#define REG_ORG		12
#define REG_CUR		14	

#define DEF_COLOR	0x0700

typedef struct console_struct console_t;

console_t consoles[VGA_NUM];
console_t * cur_console = &consoles[0];

/*
 * caller should have locked interrupt
 */
static void set_console(int reg, unsigned short val)
{
	outb(reg, VGA_CRT_INX);
	outb(0xff & (val >> 8), VGA_CRT_DATA);
	outb(reg + 1, VGA_CRT_INX);
	outb(0xff & val, VGA_CRT_DATA);
}

static void console_flush(console_t * console)
{
	unsigned long tmp, flags;
	unsigned short * vidmem;

	vidmem = (unsigned short *)console->c_realvidmem + console->c_curpos;

	if(console->c_idx){
		front_copy(vidmem, console->c_data, console->c_idx);

		console->c_idx = 0;
	}
	
	tmp  = console->c_org + cols * console->c_currow + console->c_curcol;
	if(EQ(tmp, console->c_curpos))
		return;

	console->c_curpos = tmp;

	lock(flags);	
	if(EQ(console, cur_console))
		set_console(REG_CUR, console->c_curpos);
	unlock(flags);
}

extern struct tty_struct * cur_tty;

void select_console(int index)
{
	int curidx;
	unsigned long flags;
	struct console_struct * console;

	curidx = cur_console - consoles;

	if(EQ(index, -1))
		curidx = EQ(curidx, 0) ? VGA_NUM - 1 : curidx - 1;
	else if(EQ(index, -2))
		curidx = EQ(curidx, VGA_NUM - 1) ? 0 : curidx + 1;
	else if((index < -2) || (index > VGA_NUM - 1))
		return;
	else
		curidx = index;

	console = &consoles[curidx];

	// no need to switch, return
	if(EQ(cur_console, console))
		return;	

	cur_console->c_leds = leds;

	memcpy(cur_console->c_vidmem, (void *)KERN_VA(VID_MEM), VID_SIZE);
	memcpy((void *)KERN_VA(VID_MEM), console->c_vidmem, VID_SIZE);

	cur_console->c_realvidmem = (unsigned long)cur_console->c_vidmem;
	console->c_realvidmem = KERN_VA(VID_MEM);

	cur_console = &consoles[curidx];
	cur_tty = cur_console->c_tty;

	lock(flags);

	set_console(REG_ORG, cur_console->c_org);
	set_console(REG_CUR, cur_console->c_curpos);

	leds = cur_console->c_leds;
	setleds();
	
	unlock(flags);
}

static int hpstack[16]; // 16 is enough
static int hpsidx = 0;

#define min(x, y)	((x) >= (y) ? (y) : (x))

/*
 *  history screen page up
 */
void console_begin_page_up(void)
{
	int org, hp;
	int cur_pos;
	unsigned long flags;

	hp = cur_console->c_history_pos;

	if(EQ(hp, -1)){
		hpsidx = 0;
		hp = org = cur_console->c_org;	
	}else{
		if((hp > cur_console->c_org)
			&& ((hp -  2 * cols * lines) < cur_console->c_org))
			return;

		org = hp;
	}

	hpstack[hpsidx++] = org;

	org -= cols * lines;
	if (org < cur_console->c_start)
		org = cur_console->c_end - cols * (lines - 1)
			- (cols * lines - (hp - cur_console->c_start));

	cur_pos = org + cols * lines;

	lock(flags);
	set_console(REG_ORG, org);
	set_console(REG_CUR, cur_pos);
	unlock(flags);

	// save current history postion
	cur_console->c_history_pos = org;	
}

/*
 * history screen end page up
 */
void console_end_page_up(void)
{
	unsigned long flags;

	lock(flags);
	set_console(REG_ORG, cur_console->c_org);
	set_console(REG_CUR, cur_console->c_curpos);
	unlock(flags);

	cur_console->c_history_pos = -1;
	hpsidx = 0;
}

/*
 * history screen page down
 */
void console_begin_page_down(void)
{
	unsigned long flags;
	int cur_pos, org = cur_console->c_history_pos;
	
	// The SHIFT+PAGEUP isn't invoked.
	if (EQ(org, -1) || (hpsidx < 1)){
		return;
	}

	org = hpstack[--hpsidx];
	cur_pos = org + cols * lines;

	lock(flags);	
	set_console(REG_ORG, org);
	set_console(REG_CUR, cur_pos);
	lock(flags);	

	// save current history postion
	cur_console->c_history_pos = org;
}

static void scroll_up(console_t * console)
{
	unsigned long flags;
	int start, end, howmuch;
	unsigned short * src, * dest, ch, * vidmem;

	start = console->c_scroll_start;
	end = console->c_scroll_end;
	howmuch = end - start;

	if(howmuch < (lines - 1)){
		dest = (unsigned short *)console->c_realvidmem
			+ console->c_org + start * cols;
		src = dest + cols;
		front_copy(dest, src, howmuch * cols);

		ch = ' ';
		color_ch(console, ch);

		vidmem = dest + howmuch * cols;
		vidmem_set(vidmem, ch, cols);

		return;
	}

	if((console->c_org + cols * (lines + 1)) <= console->c_end)
		console->c_org += cols;
	else{
		console_flush(console); // take me how a long time :-)

		dest = (unsigned short *)console->c_realvidmem + console->c_start;
		src = (unsigned short *)console->c_realvidmem + (console->c_org + cols);
		front_copy(dest, src, (lines - 1) * cols);

		console->c_org = console->c_start;
	}

	lock(flags);
	if(EQ(console, cur_console))
		 set_console(REG_ORG, console->c_org);
	unlock(flags);
	
	ch = ' ';
	color_ch(console, ch);

	vidmem = (unsigned short *)console->c_realvidmem
		+ (console->c_org + (lines - 1) * cols);
	vidmem_set(vidmem, ch, cols);
}

static void scroll_down(console_t * console)
{
	unsigned long flags;
	int start, end, howmuch;
	unsigned short * src, * dest, ch, * vidmem;

	start = console->c_scroll_start;
	end = console->c_scroll_end;
	howmuch = end - start;

	if(howmuch < (lines - 1)){
		src = (unsigned short *)console->c_realvidmem
			+ console->c_org + start * cols;
		dest = src + cols;
		back_copy(dest, src, howmuch * cols);

		ch = ' ';
		color_ch(console, ch);

		vidmem_set(src, ch, cols);

		return;
	}

	if((console->c_org - cols) >= console->c_start){
		console->c_org -= cols;
	}else{
		console_flush(console); // xxx

		dest = (unsigned short *)console->c_realvidmem
			+ (console->c_end - (lines - 1) * cols);
		src = (unsigned short *)console->c_realvidmem + console->c_org;
		back_copy(dest, src, (lines - 1) * cols);

		console->c_org = console->c_end - lines * cols;
	}

	lock(flags);
	if(EQ(console, cur_console))
		set_console(REG_ORG, console->c_org);
	unlock(flags);

	ch = ' ';
	color_ch(console, ch);

	vidmem = (unsigned short *)console->c_realvidmem + console->c_org;
	vidmem_set(vidmem, ch, cols);
}

#include <asm/system.h>
#include <cnix/timer.h>

static int beeping = 0;

static void beep_stop(void * data)
{
	unsigned long flags;

	lock(flags);
	outb(inb(0x61) & ~3, 0x61);	
	unlock(flags);

	beeping = 0;
}

synctimer_t beep_timer;

void beep_start(void)
{
	unsigned long flags;

	lock(flags);

	if(beeping){
		unlock(flags);
		return;
	}

	beep_timer.expire = HZ / 10;
	beep_timer.timerproc = beep_stop;
	synctimer_set(&beep_timer);

	/*
	 * (0x630 * HZ) is just larger than (TIMER_FREQ / 10),
	 * so beep_stop will be executed before beeping stops naturally.
	 */
	outb(0xb6, 0x43);
	outb(0x30, 0x42);
	outb(0x06, 0x42);
	outb(inb(0x61) | 3, 0x61);

	unlock(flags);

	beeping = 1;
}

static void beep_proc(console_t * console)
{
	beep_start();
}

static void bs_proc(console_t * console)
{
	--console->c_curcol;
	if(console->c_curcol < 0){
		--console->c_currow;
		if(console->c_currow >= 0)
			console->c_curcol += cols;
		else{
			console->c_curcol = 0;
			console->c_currow = 0;
		}
	}
}

static void nl_proc(console_t * console)
{
	if((console->c_tty->t_termios.c_oflag & OPOST)
		&& (console->c_tty->t_termios.c_oflag & ONLCR))
		console->c_curcol = 0;

	if(EQ(console->c_currow, console->c_scroll_end))
		scroll_up(console);
	else
		console->c_currow++;
}

static void cr_proc(console_t * console)
{
	console->c_curcol = 0;
}

static void tab_proc(console_t * console)
{
	console->c_curcol = (console->c_curcol + 8) & (~(8 - 1));

	if(console->c_curcol > cols - 1){
		console->c_curcol -= cols - 1;
		if(EQ(console->c_currow, console->c_scroll_end))
			scroll_up(console);
		else
			console->c_currow++;
	}
}

static void esc_proc(console_t * console)
{
	console->c_escape = 1;
}

typedef void (*char_proc_t)(console_t *);

static struct char_table_struct{
	char ch;
	char_proc_t char_proc;
}char_table[] = {
	{ '\n', nl_proc },
	{ 033, esc_proc },
	{ '\t', tab_proc },
	{ '\b', bs_proc },
	{ 007,  beep_proc },
	{ '\r', cr_proc },
};

#define CHAR_TABLE_SIZE (sizeof(char_table) / sizeof(struct char_table_struct))

static void escape0(console_t * console, unsigned char ch)
{
	if(ZERO(ch))
		return;

	if(ch < ' '){
		int i;

		for(i = 0; i < CHAR_TABLE_SIZE; i++)
			if(EQ(char_table[i].ch, ch)){
				char_table[i].char_proc(console);
				console_flush(console);
				return;
			}
	}

	if((console->c_idx > VBUFFERSIZE - 1) || (console->c_curcol > (cols - 1))){
		if(EQ(console->c_currow, console->c_scroll_end))
			scroll_up(console);
		else
			console->c_currow++;

		console->c_curcol = 0;
		console_flush(console);
	}

	console->c_data[console->c_idx] = ch;
	color_ch(console, console->c_data[console->c_idx]);

	console->c_idx++;
	console->c_curcol++;
}

/* 
 * ESC [nA, ESC [nB, ESC [nC, ESC [nD
 * ESC [sJ, ESC [sK
 * ESC [m;nH
 * ESC [nL, ESC [nM, ESC [nP, ESC [n@, ESC [nm
 * ESC M
 * state 1: see ESC, state 2: see [, state 3: see '0'-'9', state 4: see ';'
 * ...
 */

static void escape1(console_t * console, unsigned char ch)
{
	if(EQ(ch, '[')){
		int i;
		for(i = 0; i < ESC_ARGV_NUM; i++)
			console->c_esc_argv[i] = -1; // invalid value
		console->c_escape = 2;
		return;
	}else if(EQ(ch, '(')){
		console->c_escape = 9;
		return;
	}else if(EQ(ch, ')')){
		console->c_escape = 10;
		return;
	}

	switch(ch){
	case 'D': // Index
		break;
	case 'E': // Next Line
		break;
	case 'M': // Reverse index
		if(EQ(console->c_currow, console->c_scroll_start))
			scroll_down(console);
		else
			console->c_currow--;

		console_flush(console);
		break;
	case '7': // Save Cursor and attributes
		console->c_bak_currow = console->c_currow;
		console->c_bak_curcol = console->c_curcol;
		console->c_bak_curpos = console->c_curpos;
		console->c_bak_attr = console->c_attr;
		console_flush(console);
		break;
	case '8': // Restore cursor and attributes
		if(console->c_bak_curpos >= 0){
			console->c_currow = console->c_bak_currow;
			console->c_curcol = console->c_bak_curcol;
			console->c_curpos = console->c_bak_curpos;
			console->c_attr = console->c_bak_attr;
			console->c_bak_curpos = -1;
			console->c_bak_attr = DEF_COLOR;
			console_flush(console);
		}
		break;
	case '1': // Graphic proc. option: On
	case '2': // Graphic proc. option: Off
	case '=': // Keypad mode: Application
	case '>': // Keypad mode: Numeric
		break;
	default:
		break;
	}

	console->c_escape = 0;
}

#define check_col(console) \
do{ \
	if(console->c_curcol < 0) \
		console->c_curcol = 0; \
	if(console->c_curcol > cols - 1) \
		console->c_curcol = cols - 1; \
}while(0)

#define check_row(console) \
do{ \
	if(console->c_currow < 0) \
		console->c_currow = 0; \
	if(console->c_currow > lines - 1) \
		console->c_currow = lines - 1; \
}while(0)

static void esc_at(console_t * console)
{
	int v, count;	
	unsigned short * src, * dest, ch, * vidmem;
	
	v = console->c_esc_argv[0];

	v = (v <= 0) ? 0 : v - 1;
	if(v > (cols - console->c_curcol))
		v = cols - console->c_curcol;

	ch = ' ';
	color_ch(console, ch);

	count = cols - (console->c_curcol + v);

	src = (unsigned short *)console->c_realvidmem + console->c_curpos;
	dest = src + v;
	back_copy(dest, src, count);
	
	vidmem = src;
	vidmem_set(vidmem, ch, v);
}

static void esc_A(console_t * console)
{
	int v = console->c_esc_argv[0];

	console->c_currow -= ((v <= 0) ? 1 : v);
	check_row(console);
}

static void esc_B(console_t * console)
{
	int v = console->c_esc_argv[0];

	console->c_currow += ((v <= 0) ? 1 : v);
	check_row(console);
}

static void esc_C(console_t * console)
{
	int v = console->c_esc_argv[0];

	console->c_curcol += ((v <= 0) ? 1 : v);
	check_col(console);
}

static void esc_D(console_t * console)
{
	int v = console->c_esc_argv[0];

	console->c_curcol -= ((v <= 0) ? 1 : v);
	check_col(console);
}

static void esc_E(console_t * console)
{
	int v = console->c_esc_argv[0];

	console->c_currow += ((v <= 0) ? 1 : v);
	check_row(console);
	console->c_curcol = 0;
}

static void esc_F(console_t * console)
{
	int v = console->c_esc_argv[0];

	console->c_currow -= ((v <= 0) ? 1 : v);
	check_row(console);
	console->c_curcol = 0;
}

static void esc_G(console_t * console)
{
	int v = console->c_esc_argv[0];

	v = (v <= 0) ? 0 : v - 1;

	console->c_curcol = v;
	check_col(console);
}

static void esc_H(console_t * console)
{
	int v;

	v  = console->c_esc_argv[1];
	console->c_curcol = (v <= 0) ? 0 : v - 1;
	check_col(console);

	v  = console->c_esc_argv[0];
	console->c_currow = (v <= 0) ? 0 : v - 1;
	check_row(console);
}

static void esc_J(console_t * console)
{
	int v, start, count;
	unsigned short ch, * vidmem;

	v = console->c_esc_argv[0];
	if(v < 0)
		v = 0;

	switch(v){
	case 0:
		start = console->c_curpos;
		count = lines * cols - (console->c_curpos - console->c_org);
		break;
	case 1:
		start = console->c_org;
		count = console->c_curpos - console->c_org;
		break;
	case 2:
		start = console->c_org;
		count = lines * cols;
		break;
	default:
		return;
	}

	ch = ' ';
	color_ch(console, ch);

	vidmem = (unsigned short *)console->c_realvidmem + start;
	vidmem_set(vidmem, ch, count);
}

static void esc_K(console_t * console)
{
	int v, start, count;
	unsigned short ch, * vidmem;

	v = console->c_esc_argv[0];
	if(v < 0)
		v = 0;

	switch(v){
	case 0:
		start = console->c_curpos;
		count = cols - console->c_curcol;
		break;
	case 1:
		start = console->c_curpos - console->c_curcol;
		count = console->c_curcol;
		break;
	case 2:
		start = console->c_curpos - console->c_curcol;
		count = cols;
		break;
	default:
		return;
	}

	ch = ' ';
	color_ch(console, ch);

	vidmem = (unsigned short *)console->c_realvidmem + start;
	vidmem_set(vidmem, ch, count);
}

static void esc_L(console_t * console)
{
	int v, count;
	unsigned short * src, * dest, ch, * vidmem;

	v = console->c_esc_argv[0];

	v = (v <= 0) ? 0 : v - 1;
	if(v > lines - console->c_currow)
		v = lines - console->c_currow;

	ch = ' ';
	color_ch(console, ch);

	count = lines - (v + console->c_currow);

	src = (unsigned short *)console->c_realvidmem
		+ (console->c_curpos - console->c_curcol);
	dest = src + v * cols;
	back_copy(dest, src, count * cols);
	
	vidmem = src;
	vidmem_set(vidmem, ch, (v * cols));
}

static void esc_M(console_t * console)
{
	int v, count;
	unsigned short * src, * dest, ch, * vidmem;

	v = console->c_esc_argv[0];

	v = (v <= 0) ? 0 : v - 1;
	if(v > lines - console->c_currow)
		v = lines - console->c_currow;

	ch = ' ';
	color_ch(console, ch);

	count = lines - (v + console->c_currow);

	dest = (unsigned short *)console->c_realvidmem
		+ (console->c_curpos - console->c_curcol);
	src = dest + v * cols;
	
	front_copy(dest, src, count * cols);
	vidmem = (unsigned short *)dest + (count * cols);
	vidmem_set(vidmem, ch, (v * cols));
}

static void esc_P(console_t * console)
{
	int v, count;	
	unsigned short * src, * dest, ch, * vidmem;
	
	v = console->c_esc_argv[0];

	if(v <= 0)
		v = 1;
	else if(v > (cols - console->c_curcol))
		v = cols - console->c_curcol;

	ch = ' ';
	color_ch(console, ch);

	count = cols - (console->c_curcol + v);

	dest = (unsigned short *)console->c_realvidmem + console->c_curpos;
	src = dest + v;	
	front_copy(dest, src, count);

	vidmem = (unsigned short *)dest + (count << 1);
	vidmem_set(vidmem, ch, v);
}

static void esc_X(console_t * console)
{
	int v;
	unsigned short * dest, ch;
	
	v = console->c_esc_argv[0];

	if(v <= 0)
		v = 1;
	else if(v > (cols - console->c_curcol))
		v = cols - console->c_curcol;

	ch = ' ';
	color_ch(console, ch);

	dest = (unsigned short *)console->c_realvidmem + console->c_curpos;
	vidmem_set(dest, ch, v);
}

static void esc_a(console_t * console)
{
	int v = console->c_esc_argv[0];

	if(v <= 0)
		v = 1;

	console->c_curcol = v;
	check_col(console);
}

static void esc_c(console_t * console)
{
	// xxx: answer-ESC[?6c:-I'm a VT102
}

static void esc_d(console_t * console)
{
	int v = console->c_esc_argv[0];

	console->c_currow = (v <= 0) ? 0 : v - 1;
	check_row(console);
}

static void esc_e(console_t * console)
{
#if 0
	int v = console->c_esc_argv[0];

	if(v <= 0)
		v = 1;

	console->c_currow = v;
	check_row(console);
#endif
}

static void esc_g(console_t * console)
{
	// xxx: clear the tab stop at the current position
}

/*
 *					To Set		To Reset
 * Mode Name		Mode				Mode
 * Line feed/new line	New Line	ESC [20h	Line Feed	ESC [20l
 * Cursor key mode	Application	ESC [?1h	Cursor		ESC [?l
 * ANSI/VT52 mode	ANSI		N/A		VT52		ESC [?2l
 * Column mode		132 Col		ESC [?3h	80 Col		ESC [?3l
 * Scrolling mode	Smooth		ESC [?4h	Jump		ESC [?4l
 * Screen mode		Reverse		ESC [?5h	Normal		ESC [?5l
 * Origin mode		Relative	ESC [?6h	Absolute	ESC [?6l
 * Wraparound		On		ESC [?7h	Off		ESC [?7l
 * Auto repeat		On		ESC [?8h	Off		ESC [?8l
 * Interlace		On		ESC [?9h	Off		ESC [?9l
 * Graphic proc. option	On		ESC 1		Off		ESC 2
 * Keypad mode		Application	ESC =		Numeric		ESC >
 */

static void esc_h(console_t * console)
{

}

static void esc_l(console_t * console)
{

}

static unsigned char colors[] = { 0, 4, 2, 6, 1, 5, 3, 7 };

static void esc_m0(console_t * console)
{
	int v, i = 0;
	unsigned short attr = console->c_attr, mask = 0;

	v = console->c_esc_argv[0];
	switch(v){
	case 0: // normal
		console->c_attr = DEF_COLOR;
		console->c_color_reverse = 0;
		return;
	case 1: // bright, usually turns on bold
		return;
	case 2: // dim
		return;
	case 3: // underline
		return;
	case 5: // blink
		return;
	case 7: // reserve
		console->c_color_reverse = 1;
		return;
	case 8: // hidden
		return;
	default:
		break;
	}

	/*
	 * fg: 30    31  32    33     34   35      36   37
	 * bk: 40    41  42    43     44   45      46   47
	 *     black red green yellow blue magenta cyan white
	 */
	if((v >= 30) && (v <= 37)){
		v -= 30;
		i = 8;
		mask = 0xf0ff;
	}else if((v >= 40) && (v <= 47)){
		v -= 40;
		i = 12;
		mask = 0x0fff;
	}	

	if(i)
		console->c_attr = (attr & mask) | (colors[v] << i);
}

static void esc_m(console_t * console)
{
	int v;	

	v = console->c_esc_argv[0];	
	if(v < 0) // default
		v = 0;

	console->c_esc_argv[0] = v;
	esc_m0(console);

	v = console->c_esc_argv[1];
	if(v >= 0){
		console->c_esc_argv[0] = v;
		esc_m0(console);
	}

	v = console->c_esc_argv[2];
	if(v >= 0){
		console->c_esc_argv[0] = v;
		esc_m0(console);
	}

	v = console->c_esc_argv[3];
	if(v >= 0){
		console->c_esc_argv[0] = v;
		esc_m0(console);
	}
}

static void esc_n(console_t * console)
{

}

static void esc_q0(console_t * console)
{
	int v = console->c_esc_argv[0];

	if(EQ(v, 0)){
		leds = 0;
		setleds();
	}else if((v >= 1) && (v <= 3)){
		leds |= 1 << (v - 1);
		setleds();
	}
}

static void esc_q(console_t * console)
{
	int v;

	v = console->c_esc_argv[0];
	if(v < 0)
		v = 0;

	console->c_esc_argv[0] = v;
	esc_q0(console);

	v = console->c_esc_argv[1];
	if(v >= 0){
		console->c_esc_argv[0] = v;
		esc_q0(console);
	}

	v = console->c_esc_argv[2];
	if(v >= 0){
		console->c_esc_argv[0] = v;
		esc_q0(console);
	}

	v = console->c_esc_argv[3];
	if(v >= 0){
		console->c_esc_argv[0] = v;
		esc_q0(console);
	}
}

/*
 * Scrolling Region
 */
static void esc_r(console_t * console)
{	
	int start = console->c_esc_argv[0];
	int end = console->c_esc_argv[1];

	start = (start <= 0) ? 0 : start - 1;
	end = (end <= 0) ? 0 : end - 1;
	if(start >= end) // do nothing
		return;

	console->c_scroll_start = start;
	console->c_scroll_end = end;
}

static void esc_s(console_t * console)
{

}

static void esc_u(console_t * console)
{

}

typedef void (*esc_char_proc_t)(console_t *);

static struct esc_char_table_struct{
	char ch;
	esc_char_proc_t esc_char_proc;
}esc_char_table[] = {
	{ '@', esc_at },
	{ 'A', esc_A },
	{ 'B', esc_B },
	{ 'C', esc_C },
	{ 'D', esc_D },
	{ 'E', esc_E },
	{ 'F', esc_F },
	{ 'G', esc_G },
	{ 'H', esc_H },
	{ 'J', esc_J },
	{ 'K', esc_K },
	{ 'L', esc_L },
	{ 'M', esc_M },
	{ 'P', esc_P },
	{ 'X', esc_X },
	{ 'a', esc_a },
	{ 'c', esc_c },
	{ 'd', esc_d },
	{ 'e', esc_e },
	{ 'f', esc_H }, // the same with 'H'
	{ 'g', esc_g },
	{ 'h', esc_h },
	{ 'l', esc_l },
	{ 'm', esc_m },
	{ 'n', esc_n },
	{ 'q', esc_q },
	{ 'r', esc_r },
	{ 's', esc_s },
	{ 'u', esc_u },
};

#define ESC_CHAR_TABLE_SIZE \
	(sizeof(esc_char_table) / sizeof(struct esc_char_table_struct))

esc_char_proc_t esc_char_proc_table[256];

void select_console_init(int index)
{
	int i;
	unsigned long flags;
	struct console_struct * console;

	for(i = 0; i < 256; i++)
		esc_char_proc_table[i] = NULL;

	for(i = 0; i < ESC_CHAR_TABLE_SIZE; i++){
		struct esc_char_table_struct * e = &esc_char_table[i];
		esc_char_proc_table[(int)e->ch] = e->esc_char_proc;
	}

	console = &consoles[index];

	cur_console = console;
	cur_tty = cur_console->c_tty;

	cur_console->c_realvidmem = KERN_VA(VID_MEM);

	lock(flags);

	set_console(REG_ORG, cur_console->c_org);
	set_console(REG_CUR, cur_console->c_curpos);
	
	leds = cur_console->c_leds;
	setleds();

	unlock(flags);	
}

static void escape2(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[0] = ch - '0';
		console->c_escape = 3;
		return;
	}

	if(EQ(ch, '?')){
		console->c_escape = 8;
		return;
	}
	
	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	console->c_escape = 0;
}

static void escape3(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[0] = console->c_esc_argv[0] * 10 + ch - '0';
		return;
	}

	if(EQ(ch, ';')){
		console->c_escape = 4;
		return;
	}

	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	console->c_escape = 0;
}

static void escape4(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[1] = ch - '0';
		console->c_escape = 5;
		return;
	}

	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	console->c_escape = 0;
}

static void escape5(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[1] = console->c_esc_argv[1] * 10 + ch - '0';
		return;
	}

	if(EQ(ch, ';')){
		console->c_escape = 6;
		return;
	}

	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	console->c_escape = 0;
}

static void escape6(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[2] = ch - '0';
		console->c_escape = 7;
		return;
	}

	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	console->c_escape = 0;
}

static void escape7(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[2] = console->c_esc_argv[2] * 10 + ch - '0';
		return;
	}

	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	if(EQ(ch, ';')){
		console->c_escape = 11;
		return;
	}

	console->c_escape = 0;
}

static void escape8(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[0] = console->c_esc_argv[0] * 10 + ch - '0';
		return;
	}

	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	console->c_escape = 0;
}

/*
 * Character Set		G0 Designator	G1 Designator
 * United Kingdon (UK)		ESC ( A		ESC ) A		
 * United States (USASCII)	ESC ( B		ESC ) B
 * Special graphics characters	ESC ( 0		ESC ) 0
 *   and line drawing set
 * Alternate character ROM	ESC ( 1		ESC ) 1
 * Alternate character ROM	ESC ( 2		ESC ) 2
 *   special graphics characters
 */
static void escape9(console_t * console, unsigned char ch)
{
	switch(ch){
	case 'A':
	case 'B':
	case '0':
	case '1':
	case '2':
		break;
	default:
		break;
	}

	console->c_escape = 0;
}

static void escape10(console_t * console, unsigned char ch)
{
	switch(ch){
	case 'A':
	case 'B':
	case '0':
	case '1':
	case '2':
		break;
	default:
		break;
	}

	console->c_escape = 0;
}

static void escape11(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[3] = ch - '0';
		console->c_escape = 12;
		return;
	}

	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	console->c_escape = 0;
}

static void escape12(console_t * console, unsigned char ch)
{
	if((ch >= '0') && (ch <= '9')){
		console->c_esc_argv[3] = console->c_esc_argv[3] * 10 + ch - '0';
		return;
	}

	if(esc_char_proc_table[ch]){
		esc_char_proc_table[ch](console);
		console_flush(console);
	}

	console->c_escape = 0;
}

typedef void (*esc_proc_t)(console_t *, unsigned char);

static esc_proc_t esc_proc_table[] = {
	escape0,
	escape1,
	escape2,
	escape3,
	escape4,
	escape5,
	escape6,
	escape7,
	escape8,
	escape9,
	escape10,
	escape11,
	escape12,
};

void kputs(const char * s)
{
	unsigned char ch;
	console_t * console = &consoles[0]; //cur_console;

	while((ch = *s) != '\0'){
		esc_proc_table[(int)console->c_escape](console, ch);
		s++;
	}

	console_flush(console);
}

/*
 * caller should have locked tty
 */
int console_write(struct tty_struct * tty)
{
	char ch;
	const char * data;
	int i, count;
	console_t * console = tty->t_console;

	data = tty->t_userdata;
	count = tty->t_userdatacount;
	for(i = 0; i < count; i++){
#if 0
		// catch any escape char for some special program
		if(strstr(current->myname, "telnet")){
			if(data[i] == '\n')
				kputs("\\n");
			else if(data[i] == '\033')
				kputs("\\033");
			else if(data[i] < ' ')
				printk("0x%x", data[i]);
			else
				printk("%c", data[i]);
		}
#endif
		if(get_user1(&ch, &data[i]) < 0){
			console_flush(console);
			return -EFAULT;
		}

		esc_proc_table[(int)console->c_escape](console, ch);
	}

	console_flush(console);

	return count;
}

void echochar(struct tty_struct * tty, unsigned char ch)
{
	unsigned long flags;

	lockb_kbd(flags);

	if(EQ(tty->t_console, cur_console)){
		unlockb_kbd(flags);
		esc_proc_table[(int)tty->t_console->c_escape](tty->t_console, ch);

		return;
	}

	unlockb_kbd(flags);

	esc_proc_table[(int)tty->t_console->c_escape](tty->t_console, ch);
}

void console_flush1(struct tty_struct * tty)
{
	unsigned long flags;

	lockb_kbd(flags);
	if(EQ(tty->t_console, cur_console)){
		unlockb_kbd(flags);
		console_flush(tty->t_console);

		return;
	}

	unlockb_kbd(flags);
}

int console_getcol(struct tty_struct * tty)
{
	return tty->t_console->c_curcol;
}

void console_init(struct tty_struct * tty, int idx)
{
	console_t * console;
	int i, scr_size;

	if(idx > (VGA_NUM - 1))
		return;

	scr_size = (VID_SIZE >> 1) / VGA_NUM;

	console = &consoles[idx];

	tty->t_console = console;
	console->c_tty = tty;
	
	console->c_currow = 0;
	console->c_curcol = 0;

	console->c_start = 0;
	console->c_end = console->c_start + ((VID_SIZE >> 1) / cols) * cols;

	// org is not set to c_start, just for us could see boot messages
	console->c_org = console->c_end - cols * lines;
	console->c_curpos = console->c_org + console->c_currow * cols + console->c_curcol;

	console->c_escape = 0;
	console->c_attr = DEF_COLOR;
	console->c_idx = 0;

	console->c_history_pos = -1;

	// clear screen
	for(i = 0; i < (VID_SIZE >> 1); i++)
		console->c_vidmem[i] = ' ' | DEF_COLOR;

	console->c_realvidmem = ((unsigned long)console->c_vidmem);

	console->c_bak_curpos = -1;
	console->c_bak_attr = DEF_COLOR;

	console->c_scroll_start = 0;
	console->c_scroll_end = lines - 1;

	console->c_leds = 0;
	console->c_color_reverse = 0;
}
